package lejos.pc.tools;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.IOException;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.border.EmptyBorder;

import lejos.pc.comm.NXTCommException;
import lejos.pc.comm.NXTCommFactory;
import lejos.pc.comm.NXTConnector;
import lejos.pc.comm.NXTInfo;
import lejos.pc.comm.NXTSamba;
import lejos.pc.comm.SystemContext;

/***
 * GUI application to write the leJOS Virtual Machine and Menu system to the NXT
 * Flash. Created on August 15, 2008, 9:36 AM revised Nov 10, 2008 to run with
 * Java 5 Author Roger Glassey based on Andy Shaw original command line code
 */
public class NXJFlashG extends JFrame {
	private static final long serialVersionUID = 177459839585979953L;
	private static final int DEFAULT_WIDTH = 400;
	private static final int DEFAULT_HEIGHT = 500;

	private JButton goButton;
	private JLabel logLabel;
	private JTextArea logArea;
	private JProgressBar progBar;
	private JLabel progBarLabel;
	private Flasher flasher;
	
	// GUI generated by NetBeans
	/** Creates new form NXJFlashG */
	public NXJFlashG() {
		initComponents();
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	}

	/**
	 * This method is called from within the constructor to initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is always
	 * regenerated by the Form Editor.0
	 */
	private void initComponents() {
		setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		setTitle("Install leJOS NXJ Firmware in NXT");
		
		goButton = new JButton();
		goButton.setText("Flash leJOS firmware");
		goButton.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				flashStart();
			}
		});

		logArea = new JTextArea();
		logArea.setTabSize(4);
		logArea.setEditable(false);
		logLabel = new JLabel("Progress Log", SwingConstants.CENTER);

		progBar = new JProgressBar(0, 100);
		progBar.setStringPainted(true);
		progBarLabel = new JLabel(" ", SwingConstants.CENTER);
		
		JPanel northPanel = new JPanel();
		northPanel.add(goButton);

		JPanel centerPanel = new JPanel(new BorderLayout());
		centerPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
		centerPanel.setMinimumSize(new Dimension(350, 550));
		centerPanel.add(logLabel, BorderLayout.NORTH);
		centerPanel.add(new JScrollPane(logArea), BorderLayout.CENTER);

		JPanel southPanel = new JPanel(new BorderLayout());
		southPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10));
		southPanel.setMinimumSize(new Dimension(300, 50));
		southPanel.add(progBar, BorderLayout.CENTER);
		southPanel.add(progBarLabel, BorderLayout.SOUTH);
		
		JPanel contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(0, 0, 5, 0));
		contentPane.setLayout(new BorderLayout(0, 0));
		contentPane.add(northPanel, BorderLayout.NORTH);
		contentPane.add(centerPanel, BorderLayout.CENTER);
		contentPane.add(southPanel, BorderLayout.SOUTH);
		setContentPane(contentPane);
	}

	private void flashStart() {
		if (flasher != null)
			//this should actually never happen
			return;
		
		JOptionPane.showMessageDialog(NXJFlashG.this,
				"Click OK, when your NXT is connected via USB and turned on.");
		
		boolean format = 0 == JOptionPane.showConfirmDialog(
				NXJFlashG.this,
				"Do you want to erase all files on the NXT?",
				"Clear memory first", JOptionPane.YES_NO_OPTION);
		
		flasher = new Flasher(format);
		flasher.start();
		goButton.setEnabled(false);
		logArea.setText("");
	}
	
	private void flashComplete() {
		flasher = null;
		goButton.setEnabled(true);
	}

	/**
	 * @param args
	 *            the command line arguments
	 */
	public static void main(String args[])
	{
		ToolStarter.startSwingTool(NXJFlashG.class, args);
	}
	
	public static int start(String[] args)
	{
		new NXJFlashG().setVisible(true);
		return 0;
	}

	/**
	 * Inner class to to the real work
	 */
	private class Flasher extends Thread implements NXJFlashUI {
		private boolean format;

		public Flasher(boolean format) {
			this.format = format;
		}

		public void message(final String str) {
			SwingUtilities.invokeLater(new Runnable() {				
				public void run() {
					logArea.append(str + "\n");
					logArea.setCaretPosition(logArea.getDocument().getLength());
				}
			});
		}

		public void progress(final String str, final int percent) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					if (str.length() <= 0)
						progBarLabel.setText(" ");
					else
						progBarLabel.setText(str);
					progBar.setValue(percent);
				}
			});
		}

		@Override
		public void run() {
			try {
				String home = SystemContext.getNxjHome();
				NXJFlashUpdate updater = new NXJFlashUpdate(this);
				byte[] memoryImage = updater.createFirmwareImage(null, null, home);
				byte[] fs = format ? updater.createFilesystemImage() : null;
				NXTSamba nxt = openDevice(updater);
				if (nxt == null) {
					message("Unable to locate a device in firmware update mode.");
					message("Please place the device in reset mode and try again.");
					JOptionPane.showMessageDialog(NXJFlashG.this,
							"No NXT found.\nPlease check that it's turned on and connected via USB.",
							"Warning", JOptionPane.WARNING_MESSAGE);
				} else {
					try {
						updater.updateDevice(nxt, memoryImage, fs, true, true, true);
					} finally {
						nxt.close();
					}
					JOptionPane.showMessageDialog(NXJFlashG.this,
							"Good news: Flashing was successful!\n"+
							"You may now exit the application or flash another NXT.",
							"Congratulations!",
							JOptionPane.INFORMATION_MESSAGE);
				}
			} catch (Exception e) {
				//TODO better error dialog 
				JOptionPane.showMessageDialog(NXJFlashG.this,
						"Bad news: An error has occurred\n" + e, "Fatal error",
						JOptionPane.ERROR_MESSAGE);
			} finally {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						flashComplete();
					}
				});
			}
		}

		/**
		 * Locate and open an NXT device ready for the firmware to be updated.
		 * First we look for devices that are already in SAM-BA mode. If we do
		 * not find any we look for devices in normal mode and attempt to
		 * re-boot them into SAM-BA mode.
		 * 
		 * @return
		 */
		private NXTSamba openDevice(NXJFlashUpdate updater) throws NXTCommException, IOException {
			// First look to see if there are any devices already in SAM-BA mode
			NXTSamba samba = updater.openSambaDevice(0);
			if (samba != null)
				return samba;
			
			NXTInfo[] nxts;
			message("No devices in firmware update mode were found.");
			message("Searching for other NXT devices.");
			NXTConnector conn = new NXTConnector();
			nxts = conn.search(null, null, NXTCommFactory.USB);
			if (nxts.length <= 0) {
				return null;
			}
			message("Found " + nxts[0].name	+ " Address " + nxts[0].deviceAddress);
			// Force into firmware update mode.
			updater.resetDevice(nxts[0]);
			return updater.openSambaDevice(30000);
		}

	}
}
